﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Linq;
using System.Xml.Serialization;
using System.Collections.Specialized;
using System.Runtime.Serialization;
using System.Threading;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using BrainTechLLC.ThreadSafeObjects;
using WordsMatching;
using System.Collections.ObjectModel;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC
{

    //#if NO_SILVERLIGHT
    //    //[TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
    //#endif
    //    [DataContract]
    //    public class VisitorEventArgs : EventArgs
    //    {
    //        [DataMember]
    //        public int X { get; set; }
    //        [DataMember]
    //        public int Y { get; set; }
    //        [DataMember]
    //        public int Z { get; set; }

    //        [DataMember]
    //        public GameLocation Location { get; set; }

    //        public VisitorEventArgs() { }
    //        public VisitorEventArgs(int x, int y, int z, GameLocation location) { X = x; Y = y; Z = z; Location = location; }
    //    }

    //#if NO_SILVERLIGHT
    //    //[TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
    //#endif
    //    [DataContract]
    //    public class VisitExitEventArgs : VisitorEventArgs
    //    {
    //        [DataMember]
    //        public GeneralDirection Direction { get; set; }

    //        public VisitExitEventArgs() { }
    //        public VisitExitEventArgs(int x, int y, int z, GameLocation location, GeneralDirection dir) : base(x, y, z, location) { Direction = dir; }
    //    }

    //    public class LocationAndExitVisitor
    //    {
    //        public event EventHandler<VisitorEventArgs> VisitedLocation;
    //        public event EventHandler<VisitExitEventArgs> VisitedExit;

    //        protected bool[, ,] _visted;
    //        protected bool[, , ,] _visitedExits;
    //        public int MaxLengthX { get; set; }
    //        public int MaxLengthY { get; set; }
    //        public int MaxLengthZ { get; set; }

    //        public void RunVisitor(GameLocation locStart, int xStart, int yStart, int zStart)
    //        {
    //            _visted = new bool[MaxLengthX, MaxLengthY, MaxLengthZ];
    //            _visitedExits = new bool[MaxLengthX, MaxLengthY, MaxLengthZ, (int)GeneralDirection.Max];
    //            RecurseLocation(locStart, xStart, yStart, zStart, GeneralDirection.None);
    //            _visted = null;
    //            _visitedExits = null;
    //        }

    //        protected void RecurseLocation(GameLocation loc, int x, int y, int z, GeneralDirection cameFromDirection)
    //        {
    //            if (loc == null)
    //                return;

    //            if (x < 0 || y < 0 || z < 0)
    //                return;

    //            if (x >= MaxLengthX || y >= MaxLengthY || z >= MaxLengthZ)
    //                return;

    //            if (_visted[x, y, z])
    //                return;

    //            if (VisitedLocation != null)
    //                VisitedLocation(this, new VisitorEventArgs(x, y, z, loc));

    //            _visted[x, y, z] = true;
    //            int skipDirection = (int)cameFromDirection.ReverseDirection();

    //            for (int n = 0; n < (int)GeneralDirection.Max; n++)
    //            {
    //                LocationEdge edge = loc.Edges[n];

    //                if (edge != null && edge.ToGameLocation != null)
    //                {
    //                    var vect = ((GeneralDirection)n).GetDirectionVector();

    //                    int newX, newY, newZ;
    //                    newX = x + vect.X;
    //                    newY = y + vect.Y;
    //                    newZ = z + vect.Z;

    //                    if (newX < 0 || newY < 0 || newZ < 0)
    //                        continue;

    //                    if (newX >= MaxLengthX || newY >= MaxLengthY || newZ >= MaxLengthZ)
    //                        continue;

    //                    if (!_visitedExits[x, y, z, n])
    //                    {
    //                        if (VisitedExit != null)
    //                            VisitedExit(this, new VisitExitEventArgs(x, y, z, loc, (GeneralDirection)n));

    //                        _visitedExits[x, y, z, n] = true;                        
    //                    }

    //                    if (n != skipDirection)
    //                    {
    //                        if (_visted[newX, newY, newZ])
    //                            continue;

    //                        RecurseLocation(edge.ToGameLocation, newX, newY, newZ, (GeneralDirection)n);
    //                    }
    //                }
    //            }
    //        }
    //    }

    [Flags]
    public enum ControlSignal
    {
        None = 0,
        Stop = 1,
        ForceCancel = 2,
        CancelSingle = 4,
        Done = 8,
    };

    public class VisitorInformation
    {
        public ThreadSafeLookup<long, LocationAdditionalData> Lookup = new ThreadSafeLookup<long, LocationAdditionalData>();
    }

    public static class LocationAdditionalDataExt
    {
        public static LocationAdditionalData AddAdditionalData(this GameLocation loc, VisitorInformation visInfo)
        {
            var data = new LocationAdditionalData() { _location = loc };
            visInfo.Lookup.Add(loc.AssociatedID, data);
            return data;
        }

        public static LocationAdditionalData GetAdditional(this GameLocation loc, VisitorInformation visInfo)
        {
            return GetAdditional(loc.AssociatedID, visInfo);
        }

        public static LocationAdditionalData GetAdditional(this long locationId, VisitorInformation visInfo)
        {
            return visInfo.Lookup[locationId];
        }
    }

    public class LocationAdditionalData
    {
        public LocationInt _tempXYZ;

        public GameLocation _location;

        public bool _visited;

        public GameLocation _visitedFrom;
        public GeneralDirection _visitedFromDirection;

        public void Clear()
        {
            _tempXYZ = new LocationInt(0, 0, 0);
            _visited = false;
            _visitedFrom = null;
            _visitedFromDirection = GeneralDirection.None;
        }
    }

    public class ErrorEventArgs : EventArgs
    {
        public string Message { get; set; }
        public Exception Exception { get; set; }
        public ErrorEventArgs(Exception ex) { Exception = ex; }
        public ErrorEventArgs(string message) { Message = message; }
        public ErrorEventArgs() { }
    }

    public class VisitorEventArgs : EventArgs
    {
        public ControlSignal Signal { get; set; }
        public LocationInt XYZ { get; set; }
        public GameLocation Location { get; set; }
        public GameLocation VisitedFrom { get; set; }
        public GeneralDirection Direction { get; set; }

        public VisitorEventArgs() { }
        public VisitorEventArgs(LocationInt loc, GameLocation location, GameLocation visitedFrom, GeneralDirection dir) { XYZ = loc; Location = location; Direction = dir; VisitedFrom = visitedFrom; }
    }

    public class VisitExitEventArgs : VisitorEventArgs
    {
        public VisitExitEventArgs() { }
        public VisitExitEventArgs(LocationInt loc, GameLocation location, GameLocation visitedFrom, GeneralDirection dir) : base(loc, location, visitedFrom, dir) { }
    }

    public class VisitOverlapEventArgs : VisitExitEventArgs
    {
        public LocationAdditionalData ConflictInfo { get; set; }

        public VisitOverlapEventArgs() { }
        public VisitOverlapEventArgs(LocationInt loc, GameLocation location, GameLocation visitedFrom, GeneralDirection dir, LocationAdditionalData data) : base(loc, location, visitedFrom, dir) { ConflictInfo = data; }
    }

    public class LocationAndExitVisitor
    {
        public event EventHandler<VisitorEventArgs> VisitingLocation;
        public event EventHandler<VisitExitEventArgs> VisitingExit;
        public event EventHandler<VisitOverlapEventArgs> VisitOverlap;
        public event EventHandler<ErrorEventArgs> ExceptionOccurred;

        public VisitorInformation _visitorInfo;
        public ControlSignal Signal;

        public VisitorInformation VisitorInfo
        {
            get
            {
                if (_visitorInfo == null)
                    Interlocked.CompareExchange<VisitorInformation>(ref _visitorInfo, new VisitorInformation(), null);

                return _visitorInfo;
            }
            set
            {
                _visitorInfo = value;
            }
        }

        public LocationInt MaxCoordinate = new LocationInt(int.MaxValue, int.MaxValue, int.MaxValue);

        public void RunVisitor(long locationIdStart, LocationInt loc)
        {
            List<GameLocation> path = null;
            RecurseLocation(locationIdStart, loc, null, GeneralDirection.None, ref path);
            if (Signal == ControlSignal.None)
            {
                Signal = ControlSignal.Done;
            }
        }

        protected void RecurseLocation(long uniqueId, LocationInt locXYZ, GameLocation visitedFrom, GeneralDirection dirFrom, ref List<GameLocation> path)
        {
            if ((Signal & ControlSignal.Stop) == ControlSignal.Stop)
                return;

            GameLocation loc = uniqueId.Find<GameLocation>();

            if (loc == null)
            {
                if (ExceptionOccurred != null)
                    ExceptionOccurred(this, new ErrorEventArgs("Location with id " + uniqueId.ToString() + " not found."));

                return;
            }

            if (locXYZ.HasNegativeOrZero())
            {
                if (ExceptionOccurred != null)
                    ExceptionOccurred(this, new ErrorEventArgs("RecurseLocation reached negative or zero coordinates with id " + uniqueId.ToString()));

                return;
            }

            if (locXYZ.AnyGreatherThan(MaxCoordinate))
                return;

            if (path == null)
                path = new List<GameLocation>();

            var data = loc.GetAdditional(VisitorInfo);

            if (data._tempXYZ.Equals(locXYZ))
            {
                return;
            }
            else if (data._visited)
            {
                if (VisitOverlap != null)
                {
                    var args = new VisitOverlapEventArgs(locXYZ, loc, visitedFrom, dirFrom, data);
                    VisitOverlap(this, args);

                    if ((args.Signal & ControlSignal.CancelSingle) == ControlSignal.CancelSingle)
                        return;

                    if ((args.Signal & ControlSignal.ForceCancel) == ControlSignal.ForceCancel)
                    {
                        Signal |= ControlSignal.Stop;
                        return;
                    }
                }
            }

            if (data._tempXYZ.IsEmpty())
            {
                if (VisitingLocation != null)
                {
                    var args = new VisitorEventArgs(locXYZ, loc, visitedFrom, dirFrom);
                    VisitingLocation(this, args);

                    if ((args.Signal & ControlSignal.CancelSingle) == ControlSignal.CancelSingle)
                        return;

                    if ((args.Signal & ControlSignal.ForceCancel) == ControlSignal.ForceCancel)
                    {
                        Signal |= ControlSignal.Stop;
                        return;
                    }
                }

                data._tempXYZ = new LocationInt(locXYZ);
                data._visited = true;
                data._visitedFrom = visitedFrom;
                data._visitedFromDirection = dirFrom;
                path.Add(loc);

                //Console.WriteLine(string.Format("Visited {0} ({1} at {2})", loc.ShortName, loc.AssociatedID, loc.XYZ));
            }

            for (int n = 0; n < loc.Edges.Length; n++)
            {
                var edge = loc.Edges[n];

                if (edge == null)
                    continue;

                var toLocation = edge.ToGameLocation;

                if (toLocation == null)
                    continue;

                GeneralDirection dir = n.ToGeneralDirection();
                var vect = dir.GetDirectionVector();
                var locNewXYZ = locXYZ.Add(vect);

                if (VisitingExit != null)
                {
                    var args = new VisitExitEventArgs(locXYZ, loc, visitedFrom, dir);
                    VisitingExit(this, args);

                    if ((args.Signal & ControlSignal.CancelSingle) == ControlSignal.CancelSingle)
                        continue;

                    if ((args.Signal & ControlSignal.ForceCancel) == ControlSignal.ForceCancel)
                    {
                        Signal |= ControlSignal.Stop;
                        return;
                    }
                }

                RecurseLocation(toLocation.AssociatedID, locNewXYZ, loc, dir, ref path);
            }
        }
    }
}